package org.yinxue.framework.jdbc.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yinxue.framework.jdbc.constant.JdbcConstant;

import javax.sql.*;
import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 连接装饰类 <br>
 * <pre>
 * </pre>
 *
 * @author zengjian
 * @create 2018-07-08 22:03
 * @since 1.0.0
 */
public class YXConnection implements PooledConnection, Connection,JdbcConstant {

    public static final Logger LOGGER = LoggerFactory.getLogger(YXConnection.class);

    private DataSource dataSource;
    private Connection delegate;
    private Statement statement;
    private Map<String, PreparedStatement> preparedStatementMap = new HashMap<>();
    private ResultSet resultSet;
    private List<ConnectionEventListener> connectionEventListeners;
    private List<StatementEventListener> statementEventListeners;

    /**
     * 最后活跃时间
     */
    private long lastActiveTime;

    /**
     * 创建时间
     */
    private long createTime;


    private AtomicInteger state = new AtomicInteger(NO_USE);

    public YXConnection() {
    }

    public YXConnection(DataSource dataSource, Connection delegate) {
        this.dataSource = dataSource;
        this.delegate = delegate;
    }

    @Override
    public Statement createStatement() throws SQLException {
        if (statement == null) {
            statement = delegate.createStatement();
        }
        return statement;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        if (!preparedStatementMap.containsKey(sql)) {
            preparedStatementMap.put(sql, delegate.prepareStatement(sql));
        }
        return preparedStatementMap.get(sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return delegate.prepareCall(sql);
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return delegate.nativeSQL(sql);
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        delegate.setAutoCommit(autoCommit);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return delegate.getAutoCommit();
    }

    @Override
    public void commit() throws SQLException {
        delegate.commit();
    }

    @Override
    public void rollback() throws SQLException {
        delegate.rollback();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return delegate.isClosed();
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return delegate.getMetaData();
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        delegate.setReadOnly(readOnly);
    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return delegate.isReadOnly();
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
        delegate.setCatalog(catalog);
    }

    @Override
    public String getCatalog() throws SQLException {
        return delegate.getCatalog();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        delegate.setTransactionIsolation(level);
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return delegate.getTransactionIsolation();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return delegate.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
        delegate.clearWarnings();
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return delegate.createStatement(resultSetType, resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return delegate.prepareCall(sql, resultSetType, resultSetConcurrency);
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return delegate.getTypeMap();
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        delegate.setTypeMap(map);
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
        delegate.setHoldability(holdability);
    }

    @Override
    public int getHoldability() throws SQLException {
        return delegate.getHoldability();
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return delegate.setSavepoint();
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return delegate.setSavepoint(name);
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        delegate.rollback(savepoint);
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        delegate.releaseSavepoint(savepoint);
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return delegate.prepareStatement(sql, autoGeneratedKeys);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return delegate.prepareStatement(sql, columnIndexes);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return delegate.prepareStatement(sql, columnNames);
    }

    @Override
    public Clob createClob() throws SQLException {
        return delegate.createClob();
    }

    @Override
    public Blob createBlob() throws SQLException {
        return delegate.createBlob();
    }

    @Override
    public NClob createNClob() throws SQLException {
        return delegate.createNClob();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return delegate.createSQLXML();
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return delegate.isValid(timeout);
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
        delegate.setClientInfo(name, value);
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        delegate.setClientInfo(properties);
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return delegate.getClientInfo(name);
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return delegate.getClientInfo();
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return delegate.createArrayOf(typeName, elements);
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return delegate.createStruct(typeName, attributes);
    }

    @Override
    public void setSchema(String schema) throws SQLException {
        delegate.setSchema(schema);
    }

    @Override
    public String getSchema() throws SQLException {
        return delegate.getSchema();
    }

    @Override
    public void abort(Executor executor) throws SQLException {
        delegate.abort(executor);
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
        delegate.setNetworkTimeout(executor, milliseconds);
    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return delegate.getNetworkTimeout();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        if (isWrapperFor(iface)) {
            return (T) delegate;
        }
        return delegate.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return this.delegate.getClass() == iface;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return delegate;
    }

    @Override
    public void close() throws SQLException {
        // 返回到资源池中
        if (dataSource instanceof YXDataSource) {
            this.compareAndSet(YXDataSource.USING, YXDataSource.NO_USE);
            this.setLastActiveTime(System.currentTimeMillis());
            return;
        }
        // 关闭preparedStatement
        Collection<PreparedStatement> statements = preparedStatementMap.values();
        for (PreparedStatement preparedStatement : statements) {
            try {
                preparedStatement.close();
                firePreparedStatementEvent(preparedStatement, false);
            } catch (SQLException e) {
                LOGGER.error("关闭preparedStatement异常", e);
                firePreparedStatementEvent(preparedStatement, true);
            }
        }
        // 关闭connection
        try {
            delegate.close();
            fireConnectionEvent(false);
        } catch (SQLException e) {
            LOGGER.error("关闭realConnection异常", e);
            fireConnectionEvent(true);
        }
    }

    private void firePreparedStatementEvent(PreparedStatement preparedStatement, boolean closeOccurError) {
        if (statementEventListeners == null) {
            return;
        }
        StatementEvent statementEvent = new StatementEvent(this, preparedStatement);
        for (StatementEventListener statementEventListener : statementEventListeners) {
            if (!closeOccurError) {
                statementEventListener.statementClosed(statementEvent);
            } else {
                statementEventListener.statementClosed(statementEvent);
            }
        }
    }

    private void fireConnectionEvent(boolean closeOccurError) {
        if (connectionEventListeners == null) {
            return;
        }
        ConnectionEvent connectionEvent = new ConnectionEvent(this);
        for (ConnectionEventListener connectionEventListener : connectionEventListeners) {
            if (!closeOccurError) {
                connectionEventListener.connectionClosed(connectionEvent);
            } else {
                connectionEventListener.connectionErrorOccurred(connectionEvent);
            }
        }
    }

    @Override
    public void addConnectionEventListener(ConnectionEventListener listener) {
        if (connectionEventListeners == null) {
            connectionEventListeners = new ArrayList<>();
        }
        connectionEventListeners.add(listener);
    }

    @Override
    public void removeConnectionEventListener(ConnectionEventListener listener) {
        if (connectionEventListeners == null) {
            return;
        }
        connectionEventListeners.remove(listener);
    }

    @Override
    public void addStatementEventListener(StatementEventListener listener) {
        if (statementEventListeners == null) {
            statementEventListeners = new ArrayList<>();
        }
        statementEventListeners.add(listener);
    }

    @Override
    public void removeStatementEventListener(StatementEventListener listener) {
        if (statementEventListeners == null) {
            return;
        }
        statementEventListeners.remove(listener);
    }

    public Connection getDelegate() {
        return delegate;
    }

    public void setDelegate(Connection delegate) {
        this.delegate = delegate;
    }

    public boolean compareAndSet(int expect, int state){
        return this.state.compareAndSet(expect, state);
    }

    public long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(long createTime) {
        this.createTime = createTime;
    }

    public long getLastActiveTime() {
        return lastActiveTime;
    }

    public void setLastActiveTime(long lastActiveTime) {
        this.lastActiveTime = lastActiveTime;
    }
}
